<!--
 * @Author: weisheng
 * @Date: 2023-04-05 21:32:56
 * @LastEditTime: 2025-01-16 21:43:47
 * @LastEditors: weisheng
 * @Description: 水印组件
 * @FilePath: /wot-design-uni/src/uni_modules/wot-design-uni/components/wd-watermark/wd-watermark.vue
 * 记得注释
-->
<template>
    <view :class="rootClass" :style="rootStyle">
        <canvas
            v-if="!canvasOffScreenable && showCanvas"
            type="2d"
            :style="{
                height: canvasHeight + 'px',
                width: canvasWidth + 'px',
                visibility: 'hidden',
            }"
            :canvas-id="canvasId"
            :id="canvasId"
        />
    </view>
</template>

<script lang="ts">
export default {
    name: 'wd-watermark',
    options: {
        addGlobalClass: true,
        virtualHost: true,
        styleIsolation: 'shared',
    },
}
</script>

<script lang="ts" setup>
import {
    computed,
    onMounted,
    ref,
    watch,
    nextTick,
    type CSSProperties,
} from 'vue'
import {
    addUnit,
    buildUrlWithParams,
    isBase64Image,
    objToStyle,
    uuid,
} from '../common/util'
import { watermarkProps } from './types'

const props = defineProps(watermarkProps)

watch(
    () => props,
    () => {
        doReset()
    },
    { deep: true },
)

const canvasId = ref<string>(`water${uuid()}`) // canvas 组件的唯一标识符
const waterMarkUrl = ref<string>('') // canvas生成base64水印
const canvasOffScreenable = ref<boolean>(
    uni.canIUse('createOffscreenCanvas') && Boolean(uni.createOffscreenCanvas),
) // 是否可以使用离屏canvas
const pixelRatio = ref<number>(uni.getSystemInfoSync().pixelRatio) // 像素比
const canvasHeight = ref<number>(
    (props.height + props.gutterY) * pixelRatio.value,
) // canvas画布高度
const canvasWidth = ref<number>(
    (props.width + props.gutterX) * pixelRatio.value,
) // canvas画布宽度
const showCanvas = ref<boolean>(true) // 是否展示canvas

/**
 * 水印css类
 */
const rootClass = computed(() => {
    let classess: string = 'wd-watermark'
    if (props.fullScreen) {
        classess = `${classess} is-fullscreen`
    }
    return `${classess} ${props.customClass}`
})

/**
 * 水印样式
 */
const rootStyle = computed(() => {
    const style: CSSProperties = {
        opacity: props.opacity,
        backgroundSize: addUnit(props.width + props.gutterX),
    }
    if (waterMarkUrl.value) {
        style['backgroundImage'] = `url('${waterMarkUrl.value}')`
    }
    return `${objToStyle(style)};${props.customStyle}`
})

onMounted(() => {
    doInit()
})

function doReset() {
    showCanvas.value = true
    canvasHeight.value = (props.height + props.gutterY) * pixelRatio.value
    canvasWidth.value = (props.width + props.gutterX) * pixelRatio.value
    nextTick(() => {
        doInit()
    })
}

function doInit() {
    // #ifdef H5
    // h5使用document.createElement创建canvas，不用展示canvas标签
    showCanvas.value = false
    // #endif
    const {
        width,
        height,
        color,
        size,
        fontStyle,
        fontWeight,
        fontFamily,
        content,
        rotate,
        gutterX,
        gutterY,
        image,
        imageHeight,
        imageWidth,
    } = props

    // 创建水印
    createWaterMark(
        width,
        height,
        color,
        size,
        fontStyle,
        fontWeight,
        fontFamily,
        content,
        rotate,
        gutterX,
        gutterY,
        image,
        imageHeight,
        imageWidth,
    )
}

/**
 * 创建水印图片
 * @param width canvas宽度
 * @param height canvas高度
 * @param color canvas字体颜色
 * @param size canvas字体大小
 * @param fontStyle canvas字体样式
 * @param fontWeight canvas字体字重
 * @param fontFamily canvas字体系列
 * @param content canvas内容
 * @param rotate 倾斜角度
 * @param gutterX X轴间距
 * @param gutterY Y轴间距
 * @param image canvas图片
 * @param imageHeight canvas图片高度
 * @param imageWidth canvas图片宽度
 */
function createWaterMark(
    width: number,
    height: number,
    color: string,
    size: number,
    fontStyle: string,
    fontWeight: number | string,
    fontFamily: string,
    content: string,
    rotate: number,
    gutterX: number,
    gutterY: number,
    image: string,
    imageHeight: number,
    imageWidth: number,
) {
    const canvasHeight = (height + gutterY) * pixelRatio.value
    const canvasWidth = (width + gutterX) * pixelRatio.value
    const contentWidth = width * pixelRatio.value
    const contentHeight = height * pixelRatio.value
    const fontSize = size * pixelRatio.value
    // #ifndef H5
    if (canvasOffScreenable.value) {
        createOffscreenCanvas(
            canvasHeight,
            canvasWidth,
            contentWidth,
            contentHeight,
            rotate,
            fontSize,
            fontFamily,
            fontStyle,
            fontWeight,
            color,
            content,
            image,
            imageHeight,
            imageWidth,
        )
    } else {
        createCanvas(
            canvasHeight,
            contentWidth,
            rotate,
            fontSize,
            color,
            content,
            image,
            imageHeight,
            imageWidth,
        )
    }
    // #endif
    // #ifdef H5
    createH5Canvas(
        canvasHeight,
        canvasWidth,
        contentWidth,
        contentHeight,
        rotate,
        fontSize,
        fontFamily,
        fontStyle,
        fontWeight,
        color,
        content,
        image,
        imageHeight,
        imageWidth,
    )
    // #endif
}

/**
 * 创建离屏canvas
 * @param canvasHeight canvas高度
 * @param canvasWidth canvas宽度
 * @param contentWidth 内容宽度
 * @param contentHeight 内容高度
 * @param rotate 内容倾斜角度
 * @param fontSize 字体大小
 * @param fontFamily 字体系列
 * @param fontStyle 字体样式
 * @param fontWeight 字体字重
 * @param color 字体颜色
 * @param content 内容
 * @param image canvas图片
 * @param imageHeight canvas图片高度
 * @param imageWidth canvas图片宽度
 */
function createOffscreenCanvas(
    canvasHeight: number,
    canvasWidth: number,
    contentWidth: number,
    contentHeight: number,
    rotate: number,
    fontSize: number,
    fontFamily: string,
    fontStyle: string,
    fontWeight: string | number,
    color: string,
    content: string,
    image: string,
    imageHeight: number,
    imageWidth: number,
) {
    // 创建离屏canvas
    const canvas: any = uni.createOffscreenCanvas({
        height: canvasHeight,
        width: canvasWidth,
        type: '2d',
    })
    const ctx: any = canvas.getContext('2d')
    if (ctx) {
        if (image) {
            const img = canvas.createImage() as HTMLImageElement
            drawImageOffScreen(
                ctx,
                img,
                image,
                imageHeight,
                imageWidth,
                rotate,
                contentWidth,
                contentHeight,
                canvas,
            )
        } else {
            drawTextOffScreen(
                ctx,
                content,
                contentWidth,
                contentHeight,
                rotate,
                fontSize,
                fontFamily,
                fontStyle,
                fontWeight,
                color,
                canvas,
            )
        }
    } else {
        console.error('无法获取canvas上下文，请确认当前环境是否支持canvas')
    }
}

/**
 * 非H5创建canvas
 * 不支持创建离屏canvas时调用
 * @param contentHeight 内容高度
 * @param contentWidth 内容宽度
 * @param rotate 内容倾斜角度
 * @param fontSize 字体大小
 * @param color 字体颜色
 * @param content 内容
 * @param image canvas图片
 * @param imageHeight canvas图片高度
 * @param imageWidth canvas图片宽度
 */
function createCanvas(
    contentHeight: number,
    contentWidth: number,
    rotate: number,
    fontSize: number,
    color: string,
    content: string,
    image: string,
    imageHeight: number,
    imageWidth: number,
) {
    const ctx = uni.createCanvasContext(canvasId.value)
    if (ctx) {
        if (image) {
            drawImageOnScreen(
                ctx,
                image,
                imageHeight,
                imageWidth,
                rotate,
                contentWidth,
                contentHeight,
            )
        } else {
            drawTextOnScreen(
                ctx,
                content,
                contentWidth,
                rotate,
                fontSize,
                color,
            )
        }
    } else {
        console.error('无法获取canvas上下文，请确认当前环境是否支持canvas')
    }
}

/**
 * h5创建canvas
 * @param canvasHeight canvas高度
 * @param canvasWidth canvas宽度
 * @param contentWidth 水印内容宽度
 * @param contentHeight 水印内容高度
 * @param rotate 水印内容倾斜角度
 * @param fontSize 水印字体大小
 * @param fontFamily 水印字体系列
 * @param fontStyle 水印字体样式
 * @param fontWeight 水印字体字重
 * @param color 水印字体颜色
 * @param content 水印内容
 */
function createH5Canvas(
    canvasHeight: number,
    canvasWidth: number,
    contentWidth: number,
    contentHeight: number,
    rotate: number,
    fontSize: number,
    fontFamily: string,
    fontStyle: string,
    fontWeight: string | number,
    color: string,
    content: string,
    image: string,
    imageHeight: number,
    imageWidth: number,
) {
    const canvas = document.createElement('canvas')
    const ctx = canvas.getContext('2d')
    canvas.setAttribute('width', `${canvasWidth}px`)
    canvas.setAttribute('height', `${canvasHeight}px`)
    if (ctx) {
        if (image) {
            const img = new Image()
            drawImageOffScreen(
                ctx,
                img,
                image,
                imageHeight,
                imageWidth,
                rotate,
                contentWidth,
                contentHeight,
                canvas,
            )
        } else {
            drawTextOffScreen(
                ctx,
                content,
                contentWidth,
                contentHeight,
                rotate,
                fontSize,
                fontFamily,
                fontStyle,
                fontWeight,
                color,
                canvas,
            )
        }
    } else {
        console.error('无法获取canvas上下文，请确认当前环境是否支持canvas')
    }
}

/**
 * 绘制离屏文字canvas
 * @param ctx canvas上下文
 * @param content 水印内容
 * @param contentWidth 水印宽度
 * @param contentHeight 水印高度
 * @param rotate 水印内容倾斜角度
 * @param fontSize 水印字体大小
 * @param fontFamily 水印字体系列
 * @param fontStyle 水印字体样式
 * @param fontWeight 水印字体字重
 * @param color 水印字体颜色
 * @param canvas canvas实例
 */
function drawTextOffScreen(
    ctx: CanvasRenderingContext2D,
    content: string,
    contentWidth: number,
    contentHeight: number,
    rotate: number,
    fontSize: number,
    fontFamily: string,
    fontStyle: string,
    fontWeight: string | number,
    color: string,
    canvas: HTMLCanvasElement,
) {
    ctx.textBaseline = 'middle'
    ctx.textAlign = 'center'
    ctx.translate(contentWidth / 2, contentWidth / 2)
    ctx.rotate((Math.PI / 180) * rotate)
    ctx.font = `${fontStyle} normal ${fontWeight} ${fontSize}px/${contentHeight}px ${fontFamily}`
    ctx.fillStyle = color
    ctx.fillText(content, 0, 0)
    ctx.restore()
    waterMarkUrl.value = canvas.toDataURL()
}

/**
 * 绘制在屏文字canvas
 * @param ctx canvas上下文
 * @param content 水印内容
 * @param contentWidth 水印宽度
 * @param rotate 水印内容倾斜角度
 * @param fontSize 水印字体大小
 * @param color 水印字体颜色
 */
function drawTextOnScreen(
    ctx: UniApp.CanvasContext,
    content: string,
    contentWidth: number,
    rotate: number,
    fontSize: number,
    color: string,
) {
    ctx.setTextBaseline('middle')
    ctx.setTextAlign('center')
    ctx.translate(contentWidth / 2, contentWidth / 2)
    ctx.rotate((Math.PI / 180) * rotate)
    ctx.setFillStyle(color)
    ctx.setFontSize(fontSize)
    ctx.fillText(content, 0, 0)
    ctx.restore()
    ctx.draw()
    // #ifdef MP-DINGTALK
    // 钉钉小程序的canvasToTempFilePath接口与其他平台不一样
    ;(ctx as any).toTempFilePath({
        success(res: any) {
            showCanvas.value = false
            waterMarkUrl.value = res.filePath
        },
    })
    // #endif
    // #ifndef MP-DINGTALK
    uni.canvasToTempFilePath({
        canvasId: canvasId.value,
        success: (res) => {
            showCanvas.value = false
            waterMarkUrl.value = res.tempFilePath
        },
    })
    // #endif
}

/**
 * 绘制离屏图片canvas
 * @param ctx canvas上下文
 * @param img 水印图片对象
 * @param image 水印图片地址
 * @param imageHeight 水印图片高度
 * @param imageWidth 水印图片宽度
 * @param rotate 水印内容倾斜角度
 * @param contentWidth 水印宽度
 * @param contentHeight 水印高度
 * @param canvas canvas实例
 */
async function drawImageOffScreen(
    ctx: CanvasRenderingContext2D,
    img: HTMLImageElement,
    image: string,
    imageHeight: number,
    imageWidth: number,
    rotate: number,
    contentWidth: number,
    contentHeight: number,
    canvas: HTMLCanvasElement,
) {
    ctx.translate(contentWidth / 2, contentHeight / 2)
    ctx.rotate((Math.PI / 180) * Number(rotate))
    img.crossOrigin = 'anonymous'
    img.referrerPolicy = 'no-referrer'

    if (isBase64Image(image)) {
        img.src = image
    } else {
        img.src = buildUrlWithParams(image, {
            timestamp: `${new Date().getTime()}`,
        })
    }
    img.onload = () => {
        ctx.drawImage(
            img,
            (-imageWidth * pixelRatio.value) / 2,
            (-imageHeight * pixelRatio.value) / 2,
            imageWidth * pixelRatio.value,
            imageHeight * pixelRatio.value,
        )
        ctx.restore()
        waterMarkUrl.value = canvas.toDataURL()
    }
}

/**
 * 绘制在屏图片canvas
 * @param ctx canvas上下文
 * @param image 水印图片地址
 * @param imageHeight 水印图片高度
 * @param imageWidth 水印图片宽度
 * @param rotate 水印内容倾斜角度
 * @param contentWidth 水印宽度
 * @param contentHeight 水印高度
 */
function drawImageOnScreen(
    ctx: UniApp.CanvasContext,
    image: string,
    imageHeight: number,
    imageWidth: number,
    rotate: number,
    contentWidth: number,
    contentHeight: number,
) {
    ctx.translate(contentWidth / 2, contentHeight / 2)
    ctx.rotate((Math.PI / 180) * Number(rotate))

    ctx.drawImage(
        image,
        (-imageWidth * pixelRatio.value) / 2,
        (-imageHeight * pixelRatio.value) / 2,
        imageWidth * pixelRatio.value,
        imageHeight * pixelRatio.value,
    )
    ctx.restore()
    ctx.draw(false, () => {
        // #ifdef MP-DINGTALK
        // 钉钉小程序的canvasToTempFilePath接口与其他平台不一样
        ;(ctx as any).toTempFilePath({
            success(res: any) {
                showCanvas.value = false
                waterMarkUrl.value = res.filePath
            },
        })
        // #endif
        // #ifndef MP-DINGTALK
        uni.canvasToTempFilePath({
            canvasId: canvasId.value,
            success: (res) => {
                showCanvas.value = false
                waterMarkUrl.value = res.tempFilePath
            },
        })
        // #endif
    })
}
</script>

<style lang="scss" scoped>
@import './index.scss';
</style>
